{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "e1a2c442",
   "metadata": {},
   "source": [
    "可以在[Bookshop.org](https://bookshop.org/a/98697/9781098155438) 和\n",
    "[Amazon](https://www.amazon.com/_/dp/1098155432?smid=ATVPDKIKX0DER&_encoding=UTF8&tag=oreilly20-20&_encoding=UTF8&tag=greenteapre01-20&linkCode=ur2&linkId=e2a529f94920295d27ec8a06e757dc7c&camp=1789&creative=9325)获取纸制版和电子版的*Think Python 3e*."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "295ac6d7",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "from os.path import basename, exists\n",
    "\n",
    "def download(url):\n",
    "    filename = basename(url)\n",
    "    if not exists(filename):\n",
    "        from urllib.request import urlretrieve\n",
    "\n",
    "        local, _ = urlretrieve(url, filename)\n",
    "        print(\"Downloaded \" + str(local))\n",
    "    return filename\n",
    "\n",
    "download('https://gitee.com/regentsai/Think_Python_3e_CN/blob/master/thinkpython.py');\n",
    "download('https://gitee.com/regentsai/Think_Python_3e_CN/blob/master/diagram.py');\n",
    "\n",
    "import thinkpython"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5705b2a6",
   "metadata": {
    "tags": []
   },
   "source": [
    "# 第11章：元组\n",
    "\n",
    "本章再介绍一个内建的类型：元组；然后展示如何将列表，字典，元组组合在一起。本章也会展示元组赋值，以及对可变数量参数的函数有用的功能：打包和解包运算符。\n",
    "\n",
    "在练习中，我们将使用元组，列表和字典，解决更多字谜，实现高效的算法。\n",
    "\n",
    "提示：元组\"tuple\"有2种发音方式。有些人读作\"tuh-ple\"，与\"supple\"押韵。但在编程的语境下，多数人读作\"too-ple\"，与\"quadruple\"押韵。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "19474596",
   "metadata": {},
   "source": [
    "## 元组和列表相似\n",
    "\n",
    "元组是值的序列。值可以是任意类型，且用整数索引，因此元组在很多方面与列表相似。重要的区别在于，元组是不可变的。\n",
    "\n",
    "要创建元组，你可以使用逗号分隔的一组值表示。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "fb0bdca2",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tuple"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t = 'l', 'u', 'p', 'i', 'n'\n",
    "type(t)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a2ec15d8",
   "metadata": {},
   "source": [
    "通常元组用圆括号包围，尽管圆括号不是必须的。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "5a6da881",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tuple"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t = ('l', 'u', 'p', 'i', 'n')\n",
    "type(t)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9194a159",
   "metadata": {},
   "source": [
    "要创建包含1个元素的元组，你必须以逗号结尾："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "e2596ca7",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tuple"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t1 = 'p',\n",
    "type(t1)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e39b95a5",
   "metadata": {},
   "source": [
    "在圆括号中的单个值不是元组。\n",
    "\n",
    "译注：换句话说，逗号是Python解析字面量为元组的必须语法结构，圆括号不是。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "a0d350a6",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "str"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t2 = ('p')\n",
    "type(t2)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a64bfb64",
   "metadata": {},
   "source": [
    "另一种创建元组的方式是使用内建的函数`tuple`。没有参数的情况下，将创建空的元组。\n",
    "\n",
    "译注:`()`作为一个特例,可以表示空的元组."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "c9100ee4",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "()"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t = tuple()\n",
    "t"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f3447831",
   "metadata": {},
   "source": [
    "如果参数是一个序列（字符串，列表或者元组），`tuple`的结果是这个序列中元素组成的元组。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "44bd3d83",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "('l', 'u', 'p', 'i', 'n')"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t = tuple('lupin')\n",
    "t"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2e48b980",
   "metadata": {},
   "source": [
    "因为`tuple`是Python内建的函数名，你应该避免将其作为变量名。\n",
    "\n",
    "多数列表运算符也对元组有效。例如，方括号运算符可以索引一个元素。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "92e55b2c",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'l'"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t[0]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2f702785",
   "metadata": {},
   "source": [
    "切片运算符将选择一个范围内的元素。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "38ee5c2a",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "('u', 'p')"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t[1:3]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c9ed9af2",
   "metadata": {},
   "source": [
    "`+`运算符可以拼接多个元组。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "2e0e311a",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "('l', 'u', 'p', 'i', 'n')"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "tuple('lup') + ('i', 'n')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1d7dcd6d",
   "metadata": {},
   "source": [
    "`*`运算符将元组重复n次。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "8bb7d715",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "('s', 'p', 'a', 'm', 's', 'p', 'a', 'm')"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "tuple('spam') * 2 "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a53ce8bd",
   "metadata": {},
   "source": [
    "`sorted`函数对元组有效，但结果是一个列表，而不是元组。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "e653e00f",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['i', 'l', 'n', 'p', 'u']"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sorted(t)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "50e5cadc",
   "metadata": {},
   "source": [
    "`reversed`函数同样对元组有效。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "8969188d",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<reversed at 0x7ea58450e920>"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "reversed(t)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f6d973c5",
   "metadata": {},
   "source": [
    "结果是一个`reversed`对象，可以重新转化为一个列表或元组。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "65d7ebaa",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "('n', 'i', 'p', 'u', 'l')"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "tuple(reversed(t))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a7cb9ee6",
   "metadata": {},
   "source": [
    "基于目前的例子，元组看上去和列表完全一样。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8c3f381e",
   "metadata": {},
   "source": [
    "## 但是元组是不可变的\n",
    "\n",
    "如果你尝试用方括号运算符修改元组，你将遇到`TypeError`。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "b4970fe0",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "ename": "TypeError",
     "evalue": "'tuple' object does not support item assignment",
     "output_type": "error",
     "traceback": [
      "\u001b[31mTypeError\u001b[39m\u001b[31m:\u001b[39m 'tuple' object does not support item assignment\n"
     ]
    }
   ],
   "source": [
    "%%expect TypeError\n",
    "t[0] = 'L'"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "592ce99c",
   "metadata": {},
   "source": [
    "元组没有类似能够修改列表的方法，比如`append`或`remove`。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "772738cc",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "ename": "AttributeError",
     "evalue": "'tuple' object has no attribute 'remove'",
     "output_type": "error",
     "traceback": [
      "\u001b[31mAttributeError\u001b[39m\u001b[31m:\u001b[39m 'tuple' object has no attribute 'remove'\n"
     ]
    }
   ],
   "source": [
    "%%expect AttributeError\n",
    "\n",
    "t.remove('l')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "70772ba2",
   "metadata": {},
   "source": [
    "回忆一下，属性attribute是与对象联系在一起的变量或者方法。这个错误信息表明，元组没有名为`remove`的方法。\n",
    "\n",
    "因为元组不可变，所以它们是可哈希的，可以作为字典的键。\n",
    "例如，以下字典包含两项，每个项是元组指向整数的映射。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "37e67042",
   "metadata": {},
   "outputs": [],
   "source": [
    "d = {}\n",
    "d[1, 2] = 3\n",
    "d[3, 4] = 7"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "47ba17ab",
   "metadata": {},
   "source": [
    "我们可以像这样查询字典："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "d809a490",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "3"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "d[1, 2]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f2c0a354",
   "metadata": {},
   "source": [
    "译注：列表不能将元组作为索引。在流行的第三方数值库中，多维数组可以将元组作为索引，语法更简明。将元组作为键或者索引时，通常省略圆括号。\n",
    "\n",
    "如果我们有一个指向元组的变量，也可以将该变量作为键。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "dfc42a8b",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "7"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t = (3, 4)\n",
    "d[t]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2ea8fc3c",
   "metadata": {},
   "source": [
    "元组也可以作为字典中的值。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "2debf30c",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'key': ('a', 'b', 'c')}"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t = tuple('abc')\n",
    "d = {'key': t}\n",
    "d"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "25655ab3",
   "metadata": {},
   "source": [
    "## 元组赋值\n",
    "\n",
    "你可以将一个包含变量的元组放在赋值语句的左边，一个包含值的元组放在右边。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "1e94ea37",
   "metadata": {},
   "outputs": [],
   "source": [
    "a, b = 1, 2"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "92c00ceb",
   "metadata": {},
   "source": [
    "值从左到右分别赋值给这些变量。在这个例子里，`a`获得值`1`，`b`获得值`2`。我们可以像这样显示结果："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "99c96c7f",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(1, 2)"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "a, b"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6362b36e",
   "metadata": {},
   "source": [
    "更通用的地方在于，如果赋值语句的左边是一个元组，赋值语句的右边可以是任何序列（字符串，列表，元组）。比如，要将一个邮件地址分割为用户名和域名，你可以这样写："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "b67881ed",
   "metadata": {},
   "outputs": [],
   "source": [
    "email = 'monty@python.org'\n",
    "username, domain = email.split('@')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d134a94c",
   "metadata": {},
   "source": [
    "`split`的返回值是一个列表，在这个例子里包含两个元素，第1个元素赋值给`username`，第2个元素赋值给`domain`。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "b4515e2b",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "('monty', 'python.org')"
      ]
     },
     "execution_count": 24,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "username, domain"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5a7e3c62",
   "metadata": {},
   "source": [
    "左边变量的数量必须和右边序列中值的数量一致，否则将遇到`ValueError`。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "8e5b4a14",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "ename": "ValueError",
     "evalue": "too many values to unpack (expected 2)",
     "output_type": "error",
     "traceback": [
      "\u001b[31mValueError\u001b[39m\u001b[31m:\u001b[39m too many values to unpack (expected 2)\n"
     ]
    }
   ],
   "source": [
    "%%expect ValueError\n",
    "a, b = 1, 2, 3"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "808c2928",
   "metadata": {},
   "source": [
    "元组赋值在你想要交换两个变量时很有用。使用传统的赋值语句，你必须像这样创建一个临时变量："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "2389d6de",
   "metadata": {},
   "outputs": [],
   "source": [
    "temp = a\n",
    "a = b\n",
    "b = temp"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "98496d02",
   "metadata": {},
   "source": [
    "这是可行的，但使用元组赋值，我们可以做相同的事，但不需要临时变量。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "5512edec",
   "metadata": {},
   "outputs": [],
   "source": [
    "a, b = b, a"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a66a87bc",
   "metadata": {},
   "source": [
    "这个语句有效的原因在于，所有右边的表达式在赋值之前就求值。\n",
    "\n",
    "译注：增强赋值语句与元组交换赋值语句相同，右边的表达式先求值再执行赋值操作，因此元组交换赋值语句右边的变量也必须初始化。\n",
    "\n",
    "我们也可以在`for`语句中使用元组赋值，例如，要遍历字典中的项，我们可以使用字典的`items`方法。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "651ab417",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "one -> 1\n",
      "two -> 2\n"
     ]
    }
   ],
   "source": [
    "d = {'one': 1, 'two': 2}\n",
    "\n",
    "for item in d.items():\n",
    "    key, value = item\n",
    "    print(key, '->', value)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dd0d4feb",
   "metadata": {},
   "source": [
    "对于每次循环，`item`被赋值为一个键值对组成的元组。\n",
    "\n",
    "我们可以更简洁地写这个循环："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "id": "2c0b7d47",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "one -> 1\n",
      "two -> 2\n"
     ]
    }
   ],
   "source": [
    "for key, value in d.items():\n",
    "    print(key, '->', value)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f0513578",
   "metadata": {},
   "source": [
    "在每次循环时，键和值直接赋值给`key`和`value`变量。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "efedeb37",
   "metadata": {},
   "source": [
    "## 将元组作为返回值\n",
    "\n",
    "严格来说，函数只能返回一个值，但如果值是元组，那么效果就与返回多个值一样。例如，如果你想要将两个整数相除，计算商数和余数，分别使用`x//y`和`x%y`并不高效。同时计算它们会更好。\n",
    "\n",
    "内建的函数`divmod`接受两个数，返回包含2个值的元组，即商数和余数。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "id": "fff80eaa",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(2, 1)"
      ]
     },
     "execution_count": 31,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "divmod(7, 3)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "33f3c57d",
   "metadata": {},
   "source": [
    "我们可以使用元组赋值将结果分别存储到2个变量中。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "id": "4a0eb2a9",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "2"
      ]
     },
     "execution_count": 32,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "quotient, remainder = divmod(7, 3)\n",
    "quotient"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "id": "d74ba1b6",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1"
      ]
     },
     "execution_count": 33,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "remainder"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "15079805",
   "metadata": {},
   "source": [
    "以下是返回元组的函数示例："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "id": "dad3b3bb",
   "metadata": {},
   "outputs": [],
   "source": [
    "def min_max(t):\n",
    "    return min(t), max(t)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "43c4e1e0",
   "metadata": {},
   "source": [
    "`max`和`min`是内建的函数，分别寻找序列中的最大值与最小值。`min_max`计算这两个值，并返回包含它们的元组。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "id": "fbd90b0e",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(1, 4)"
      ]
     },
     "execution_count": 35,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "min_max([2, 4, 1, 3])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "86b60e71",
   "metadata": {},
   "source": [
    "我们可以将结果元组赋值给变量："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "id": "5a101efb",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(1, 4)"
      ]
     },
     "execution_count": 36,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "low, high = min_max([2, 4, 1, 3])\n",
    "low, high"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "112b5aa2",
   "metadata": {
    "tags": []
   },
   "source": [
    "## 实参打包\n",
    "\n",
    "函数可以接受可变数量的实参。以`*`运算符开始的形参将接受的实参**打包pack**为一个元组。例如，以下函数接受任意数量的实参，计算算术平均数："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "id": "0a33e2d0",
   "metadata": {},
   "outputs": [],
   "source": [
    "def mean(*args):\n",
    "    return sum(args) / len(args)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6044fc1b",
   "metadata": {},
   "source": [
    "这个形参可以是任何变量名，但习惯上使用`args`。我们可以像这样调用函数："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "id": "336a08ca",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "2.0"
      ]
     },
     "execution_count": 38,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "mean(1, 2, 3)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a5e8b158",
   "metadata": {},
   "source": [
    "译注:`*args`可以接受0个实参,形成空元组.对于上面这个函数,传入0个实参会导致ZeroDivisionError,因为分母为0.\n",
    "\n",
    "如果你有一个值的序列，你想要将其以多个实参的形式传递给函数，你可以使用`*`将该序列**解包unpack**。\n",
    "例如，`divmod`接受恰好两个实参，如果你传入一个元组作为实参，你将遇到类型错误。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "id": "991810bc",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "ename": "TypeError",
     "evalue": "divmod expected 2 arguments, got 1",
     "output_type": "error",
     "traceback": [
      "\u001b[31mTypeError\u001b[39m\u001b[31m:\u001b[39m divmod expected 2 arguments, got 1\n"
     ]
    }
   ],
   "source": [
    "%%expect TypeError\n",
    "t = (7, 3)\n",
    "divmod(t)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5a9110db",
   "metadata": {},
   "source": [
    "尽管元组包含两个元素，元组本身算作一个实参。如果你将元组解包，它会被处理成两个实参。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "id": "f25ebee1",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(2, 1)"
      ]
     },
     "execution_count": 40,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "divmod(*t)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "da554863",
   "metadata": {},
   "source": [
    "打包和拆包对适配现存函数的行为很有用。例如，下面这个函数接受任意数量的实参，移除最小和最大值，计算剩余实参的平均值。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "id": "7ad64412",
   "metadata": {},
   "outputs": [],
   "source": [
    "def trimmed_mean(*args):\n",
    "    low, high = min_max(args)\n",
    "    trimmed = list(args)\n",
    "    trimmed.remove(low)\n",
    "    trimmed.remove(high)\n",
    "    return mean(*trimmed)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d1e05e49",
   "metadata": {},
   "source": [
    "首先它将实参打包到`args`形参，使用`min_max`找到最小和最大的元素；然后将`args`转化为列表，从而可以使用方法；最后它将列表解包，从而能够作为分隔的实参，传递给`mean`函数。\n",
    "\n",
    "以下是这个函数的效果："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "id": "b2863701",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "4.0"
      ]
     },
     "execution_count": 42,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "mean(1, 2, 3, 10)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "id": "cc1afa29",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "2.5"
      ]
     },
     "execution_count": 43,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "trimmed_mean(1, 2, 3, 10)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "35e04996",
   "metadata": {},
   "source": [
    "这种“被修剪”的平均值被用于一些有主观评价的体育运动中，比如跳水和体操，以减少偏离其他裁判评分的单个裁判的影响。\n",
    "\n",
    "译注：拆包和打包语法结合起来，可以直接将一个序列灵活分割为前（中）后部分。感兴趣的请自行探索。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c4572cd2",
   "metadata": {},
   "source": [
    "## zip\n",
    "\n",
    "元组在遍历两个序列并对相应元素执行运算时很有用。比如，假设有两个队伍打了七次比赛，我们用两个列表分别记录两队的分数。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "id": "ad3e6f81",
   "metadata": {},
   "outputs": [],
   "source": [
    "scores1 = [1, 2, 4, 5, 1, 5, 2]\n",
    "scores2 = [5, 5, 2, 2, 5, 2, 3]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b44f228b",
   "metadata": {},
   "source": [
    "让我们看看每个队赢了多少次比赛。我们将使用`zip`函数，它是内建的函数，接受两个或更多序列，返回一个**zip对象 zip object**，因为它将这些序列的元素像拉链齿一样配对。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "id": "9ce313ce",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<zip at 0x7ea584547c40>"
      ]
     },
     "execution_count": 45,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "zip(scores1, scores2)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9adcf8f9",
   "metadata": {},
   "source": [
    "我们可以使用zip对象遍历序列的配对元素。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "id": "321d9c30",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(1, 5)\n",
      "(2, 5)\n",
      "(4, 2)\n",
      "(5, 2)\n",
      "(1, 5)\n",
      "(5, 2)\n",
      "(2, 3)\n"
     ]
    }
   ],
   "source": [
    "for pair in zip(scores1, scores2):\n",
    "     print(pair)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "51d1dabb",
   "metadata": {},
   "source": [
    "循环中的每轮次中，`pair`被赋值为每次比赛比分的元组。因此我们可以将比分解包赋值给2个变量，计算第1个队伍胜利的次数："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "id": "7eb73d5d",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "3"
      ]
     },
     "execution_count": 47,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "wins = 0\n",
    "for team1, team2 in zip(scores1, scores2):\n",
    "    if team1 > team2:\n",
    "        wins += 1\n",
    "        \n",
    "wins"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ad740fcd",
   "metadata": {},
   "source": [
    "第一个队伍只赢了3场比赛，输掉了整个系列比赛。\n",
    "\n",
    "如果你有两个列表，你想要一个配对元素的列表，你可以使用`zip`与`list`。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "id": "9529baa8",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[(1, 5), (2, 5), (4, 2), (5, 2), (1, 5), (5, 2), (2, 3)]"
      ]
     },
     "execution_count": 48,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t = list(zip(scores1, scores2))\n",
    "t"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ec4249fa",
   "metadata": {},
   "source": [
    "结果是元组的列表，所以我们可以像这样获得最后一场比赛的结果："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "id": "dbde77b8",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(2, 3)"
      ]
     },
     "execution_count": 49,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t[-1]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "436486b9",
   "metadata": {},
   "source": [
    "如果你有一组键和一组值，你可以使用`zip`和`dict`创建字典。例如，以下是创建每个字母与字母表中位置的映射字典："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "id": "dbb7d0b3",
   "metadata": {},
   "outputs": [],
   "source": [
    "letters = 'abcdefghijklmnopqrstuvwxyz'\n",
    "numbers = range(len(letters))\n",
    "letter_map = dict(zip(letters, numbers))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b4de6974",
   "metadata": {},
   "source": [
    "然后我们可以查询一个字母在字母表中的索引。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "id": "49e3fd8e",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(0, 25)"
      ]
     },
     "execution_count": 51,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "letter_map['a'], letter_map['z']"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cc632542",
   "metadata": {},
   "source": [
    "在这个映射中，`'a'`的索引为`0`，`'z'`的索引为`25`。\n",
    "\n",
    "如果你需要遍历一个序列的元素和它们的索引，你可以使用内建的函数`enumerate`。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "id": "9e4f3e51",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<enumerate at 0x7ea584562a20>"
      ]
     },
     "execution_count": 52,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "enumerate('abc')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "92ad45bb",
   "metadata": {},
   "source": [
    "结果是一个**enumerate对象 enumerate object**，可以用于遍历一个成对元素的序列，每一对包含给定序列中元素的索引（默认从0开始）和元素值。\n",
    "\n",
    "译注：`enumerate`直译为枚举，列举，历数，该类型是专为迭代设计的。Python作为C语言的衍生语言，`Enum`类型的对象才是通常意义上的枚举对象。因此`enumerate`保留英文原文。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "id": "c1dcb46d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0 a\n",
      "1 b\n",
      "2 c\n"
     ]
    }
   ],
   "source": [
    "for index, element in enumerate('abc'):\n",
    "    print(index, element)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cf0b55d7",
   "metadata": {},
   "source": [
    "## 比较和排序\n",
    "\n",
    "关系运算符对元组和其他序列有效。例如，如果你对两个元组使用`<`运算符，它会从每个元组的第一个元素开始比较。如果相等，它会继续比较下一对元素，以此类推，直到找到不同的一对。\n",
    "\n",
    "译注：回忆[第8章](chap08.ipynb)关于字符串的比较运算符。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "id": "aed20c28",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 54,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "(0, 1, 2) < (0, 3, 4)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "65ceea86",
   "metadata": {},
   "source": [
    "后面的元素不会被考虑，即使它们真的很大。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "id": "4d9e73b3",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 55,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "(0, 1, 2000000) < (0, 3, 4)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "55e4a35e",
   "metadata": {},
   "source": [
    "对于包含元组的列表而言，这种比较元组的方式对排序或者寻找最大最小值很有用。例如，让我们寻找单词中最常用的字母。在之前的章节，我们编写了`value_counts`函数，接受一个字符串，返回一个映射字母到出现次数的字典。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "id": "2077dfa9",
   "metadata": {},
   "outputs": [],
   "source": [
    "def value_counts(string):\n",
    "    counter = {}\n",
    "    for letter in string:\n",
    "        if letter not in counter:\n",
    "            counter[letter] = 1\n",
    "        else:\n",
    "            counter[letter] += 1\n",
    "    return counter"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a80012c1",
   "metadata": {},
   "source": [
    "以下是字符串`'banana'`的结果。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "id": "b3d40516",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'b': 1, 'a': 3, 'n': 2}"
      ]
     },
     "execution_count": 57,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "counter = value_counts('banana')\n",
    "counter"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cc1ea4a7",
   "metadata": {},
   "source": [
    "对于3个项，我们可以直接看出最频繁的字母是`a`，但当项数增多时，自动排序更有用。\n",
    "\n",
    "我们可以像这样获取`counter`的项。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "id": "8288c28f",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "dict_items([('b', 1), ('a', 3), ('n', 2)])"
      ]
     },
     "execution_count": 58,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "items = counter.items()\n",
    "items"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ac8dea7a",
   "metadata": {},
   "source": [
    "该结果是一个`dict_items`对象，它的行为类似一个元组组成的列表，所以我们可以像这样进行排序。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "id": "bbbade35",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[('a', 3), ('b', 1), ('n', 2)]"
      ]
     },
     "execution_count": 59,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sorted(items)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b690d17a",
   "metadata": {},
   "source": [
    "默认行为按每个元组的第1个元素进行排序，第1个元素相等则再使用第2个元素排序。\n",
    "\n",
    "然而，要寻找出现次数最多的字母，我们想使用第2个元素进行排序。我们可以编写一个函数，接受元组，返回第2个元素来满足需求。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 60,
   "id": "a4c31795",
   "metadata": {},
   "outputs": [],
   "source": [
    "def second_element(t):\n",
    "    return t[1]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b964aa14",
   "metadata": {},
   "source": [
    "然后我们可以传递该函数给`sorted`的可选参数`key`，`key`参数表示该函数应该用来计算每个项的**排序键sort key**。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 61,
   "id": "f3d3619a",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[('b', 1), ('n', 2), ('a', 3)]"
      ]
     },
     "execution_count": 61,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sorted_items = sorted(items, key=second_element)\n",
    "sorted_items"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4dc96848",
   "metadata": {},
   "source": [
    "排序键决定了列表中项的顺序。出现次数最少的字母在第1位，最多的在最后1位。所以我们可以像这样找到出现次数最多的字母。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 62,
   "id": "f078c8a6",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "('a', 3)"
      ]
     },
     "execution_count": 62,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sorted_items[-1]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d0d8b999",
   "metadata": {},
   "source": [
    "如果我们只想要最大值，我们不需要对列表排序。我们可以使用`max`函数，它也接受`key`可选参数。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 63,
   "id": "54030d8f",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "('a', 3)"
      ]
     },
     "execution_count": 63,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "max(items, key=second_element)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8a8327df",
   "metadata": {},
   "source": [
    "要找到最小值，我们可以用相同的方式调用`min`。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a62394a5",
   "metadata": {},
   "source": [
    "## 反转字典\n",
    "\n",
    "假设你想要反转字典，查询值对应的键。比如，如果你有一个映射单词到出现次数的字典，你可以创建映射出现次数到对应单词的字典。\n",
    "\n",
    "但是这会导致一个问题：字典中的键必须是唯一的，值不需要唯一。在单词计数器中，有很多单词出现的次数相同。\n",
    "\n",
    "一种反转字典的方式是将值设置为原始字典的键的列表。例如，让我们对`parrot`中的字母进行计数。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 64,
   "id": "ef158f81",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'p': 1, 'a': 1, 'r': 2, 'o': 1, 't': 1}"
      ]
     },
     "execution_count": 64,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "d =  value_counts('parrot')\n",
    "d"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f4570eae",
   "metadata": {},
   "source": [
    "如果我们反转这个字典，结果应该为`{1: ['p', 'a', 'o', 't'], 2: ['r']}`，表示出现1次的字母为`'p'`, `'a'`, `'o'`, `'t'`，出现2次的是`'r'`。\n",
    "\n",
    "以下函数接受一个字典，返回反转后的新字典。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 65,
   "id": "d3607b8d",
   "metadata": {},
   "outputs": [],
   "source": [
    "def invert_dict(d):\n",
    "    new = {}\n",
    "    for key, value in d.items():\n",
    "        if value not in new:\n",
    "            new[value] = [key]\n",
    "        else:\n",
    "            new[value].append(key)\n",
    "    return new"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ca5fa025",
   "metadata": {},
   "source": [
    "`for`语句遍历键和值，如果旧值不是新字典的键，加入到新字典的键，新值是包含单个元素（旧键）的列表；如果旧值已经是新字典的键，将对应的旧键追加到已存在的列表中。\n",
    "\n",
    "我们可以像这样测试："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 66,
   "id": "692d9cf8",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{1: ['p', 'a', 'o', 't'], 2: ['r']}"
      ]
     },
     "execution_count": 66,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "invert_dict(d)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4cfb1693",
   "metadata": {},
   "source": [
    "我们获得了期望的结果。\n",
    "\n",
    "这是第1个字典的值是列表的例子。我们将看到更多例子。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6d138cd7",
   "metadata": {
    "tags": []
   },
   "source": [
    "## 调试\n",
    "\n",
    "列表，字典和元组都是**数据结构 data structures**。在本章我们开始看到复合的数据结构，比如元组的列表，以元组为键的字典，以列表为值的字典。\n",
    "\n",
    "复合数据结构很有用，但它们容易产生类型，大小或者结构的错误。例如，如果某函数接受一个整数的列表，但你只提供了一个整数，该函数可能无法正确运行。\n",
    "\n",
    "要帮助调试这种错误，我编写了一个模块`structshape`，提供函数`structshape`，接受任何类型的数据结构，返回汇总结构信息的字符串。你可以从<https://gitee.com/regentsai/Think_Python_3e_CN/blob/master/structshape.py>下载。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 67,
   "id": "e9f03e91",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "download('https://gitee.com/regentsai/Think_Python_3e_CN/blob/master/structshape.py');"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "646f4d55",
   "metadata": {},
   "source": [
    "我们可以像这样导入："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 68,
   "id": "90ab624a",
   "metadata": {},
   "outputs": [],
   "source": [
    "from structshape import structshape"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "86cc6ccc",
   "metadata": {},
   "source": [
    "以下是一个简单列表的示例。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 69,
   "id": "6794330f",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'list of 3 int'"
      ]
     },
     "execution_count": 69,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t = [1, 2, 3]\n",
    "structshape(t)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9de4f6ec",
   "metadata": {},
   "source": [
    "列表的列表信息如下："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 70,
   "id": "54cd185b",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'list of 3 list of 2 int'"
      ]
     },
     "execution_count": 70,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t2 = [[1,2], [3,4], [5,6]]\n",
    "structshape(t2)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aced9984",
   "metadata": {},
   "source": [
    "如果列表的元素不相同，`structshape`按照类型进行分组。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 71,
   "id": "04028afd",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'list of (3 int, float, 2 str, 2 list of int, int)'"
      ]
     },
     "execution_count": 71,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t3 = [1, 2, 3, 4.0, '5', '6', [7], [8], 9]\n",
    "structshape(t3)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f63ff690",
   "metadata": {},
   "source": [
    "以下是元组的列表："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 72,
   "id": "b5d45c88",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'list of 3 tuple of (int, str)'"
      ]
     },
     "execution_count": 72,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "s = 'abc'\n",
    "lt = list(zip(t, s))\n",
    "structshape(lt)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c9ec67eb",
   "metadata": {},
   "source": [
    "以下是将整数映射到字符串的一个字典："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 73,
   "id": "15131907",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'dict of 3 int->str'"
      ]
     },
     "execution_count": 73,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "d = dict(lt) \n",
    "structshape(d)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f29bb82b",
   "metadata": {},
   "source": [
    "如果你在跟踪数据结构时出现问题，`structshape`可以提供帮助。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "79d93082",
   "metadata": {},
   "source": [
    "## 术语表\n",
    "- **打包pack**：将多个实参收集到元组中；\n",
    "- **拆包unpack**：将元组或者其他序列处理为多个实参；\n",
    "- **zip对象 zip object**：内建函数`zip`的调用结果，可以用来遍历一组元组。\n",
    "- **enumerate对象 enumerate object**：内建函数`zip`的调用结果，可以用来遍历一组元组。\n",
    "- **排序键sort key**：一个值，或者计算值的函数，用于排序集合中的元素；\n",
    "- **数据结构 data structure**：值的集合，经过组织后方便高效地执行特定操作。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1471b3c0",
   "metadata": {},
   "source": [
    "## 练习"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 74,
   "id": "c65d68d2",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Exception reporting mode: Verbose\n"
     ]
    }
   ],
   "source": [
    "# 这个单元格让Jupyter在出现运行时故障时提供更多调试信息。\n",
    "# 在进行练习前先运行本单元格。\n",
    "\n",
    "%xmode Verbose"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "97a0352d",
   "metadata": {},
   "source": [
    "### 询问虚拟助手\n",
    "\n",
    "本章的练习比前几章更难，因此我鼓励你寻求虚拟助手的帮助。当你提出更困难的问题时，你可能会发现回答无法一次性正确，因此这是练习琢磨好的提示词并逐步精炼的好机会。\n",
    "\n",
    "你可以考虑的一个策略是将大问题分解为可以解决的简单函数。要求虚拟助手编写这些函数并测试它们。然后，一旦这些函数有效，要求虚拟助手提供原始问题的解法。\n",
    "\n",
    "对于下面的一些练习，我提供了一些建议，例如使用哪些数据结构和算法。你可能会发现这些建议很有用，但它们同时也是提交给虚拟助手的好提示词。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f90e011f",
   "metadata": {},
   "source": [
    "### 练习\n",
    "\n",
    "在本章我说过，元组可以作为字典中的键，因为它是可哈希的，可哈希来源于不可变性。但这并不总是正确。\n",
    "\n",
    "如果元组中包含一个可变的值，比如列表或者字典，元组包含了不可哈希的元素，那么元组就不再可哈希了。例如，以下元组包含两个整数列表。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 75,
   "id": "4416fe4a",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "([1, 2, 3], [4, 5])"
      ]
     },
     "execution_count": 75,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "list0 = [1, 2, 3]\n",
    "list1 = [4, 5]\n",
    "\n",
    "t = (list0, list1)\n",
    "t"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "02799077",
   "metadata": {},
   "source": [
    "编写一行代码，追加`6`到`t`中的第2个列表中。如果你显示`t`，结果应该为`([1, 2, 3], [4, 5, 6])`。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 76,
   "id": "e6eda0e4",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# 在这解答"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "644b1dfb",
   "metadata": {},
   "source": [
    "尝试创建一个字典，将`t`映射到字符串，确认你会遇到`TypeError`。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 77,
   "id": "4fae1acc",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# 在这解答"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fb77a352",
   "metadata": {},
   "source": [
    "关于这个话题，询问虚拟助手，“Python的元组总是可哈希的吗？”"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bdfc8c27",
   "metadata": {
    "tags": []
   },
   "source": [
    "### 练习\n",
    "\n",
    "本章我们创建了从字母到字母表索引的字典。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 76,
   "id": "855c7ed2",
   "metadata": {},
   "outputs": [],
   "source": [
    "letters = 'abcdefghijklmnopqrstuvwxyz'\n",
    "numbers = range(len(letters))\n",
    "letter_map = dict(zip(letters, numbers))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a8cd720b",
   "metadata": {},
   "source": [
    "例如，`'a'`的索引为`0`。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 77,
   "id": "3c921f68",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "0"
      ]
     },
     "execution_count": 77,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "letter_map['a']"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a04c25db",
   "metadata": {},
   "source": [
    "另一方面，我们可以使用列表索引。例如，`'b'`的索引为`1`。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 78,
   "id": "b029b0da",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'b'"
      ]
     },
     "execution_count": 78,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "letters[1]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "165ab770",
   "metadata": {},
   "source": [
    "我们可以使用`letter_map`和`letters`，通过凯撒密码Caesar cipher进行编码和解码。\n",
    "\n",
    "凯撒密码是一种弱的加密方法，通过将字母表中的每个字母偏移固定位数，必要时回到字母表开头。比如，`'a'`偏移2后变为`'c'`，`'z'`偏移1后变为`'a'`。\n",
    "\n",
    "编写函数`shift_word`，接受一个字符串和一个整数，返回一个输入字符串偏移之后的字符串。\n",
    "\n",
    "要测试这个函数，确保\"cheer\"偏移7后变为\"jolly\"，\"melon\"偏移16后变为\"cubed\"。\n",
    "\n",
    "提示：使用求余运算符让字母表的`'z'`回到`'a'`。遍历单词的字母，偏移每个单词，并将结果追加到一个字母列表中。然后使用`join`将字母拼接为字符串。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e7478b18",
   "metadata": {
    "tags": []
   },
   "source": [
    "你可以从这个框架开始。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 81,
   "id": "1cc07036",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "def shift_word(word, n):\n",
    "    \"\"\"偏移`word`的字母n位。\n",
    "    \n",
    "    >>> shift_word('cheer', 7)\n",
    "    'jolly'\n",
    "    >>> shift_word('melon', 16)\n",
    "    'cubed'\n",
    "    \"\"\"\n",
    "    return None"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 82,
   "id": "96560a0e",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 在这解答"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 79,
   "id": "c026c6d1",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'jolly'"
      ]
     },
     "execution_count": 79,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "shift_word('cheer', 7)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 80,
   "id": "5814999d",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'cubed'"
      ]
     },
     "execution_count": 80,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "shift_word('melon', 16)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "39a67af9",
   "metadata": {
    "tags": []
   },
   "source": [
    "你可以使用`doctest`测试函数。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 85,
   "id": "9464d140",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "from doctest import run_docstring_examples\n",
    "\n",
    "def run_doctests(func):\n",
    "    run_docstring_examples(func, globals(), name=func.__name__)\n",
    "\n",
    "run_doctests(shift_word)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "779f13af",
   "metadata": {},
   "source": [
    "### 练习\n",
    "\n",
    "编写函数`most_frequent_letters`，接受一个字符串，按出现次数降序排列字母。\n",
    "\n",
    "要按降序获取元素，你可以联合使用`reversed`和`sorted`，或者设置`sorted`的关键字参数`reverse=True`。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d71923e6",
   "metadata": {
    "tags": []
   },
   "source": [
    "你可以使用以下函数框架。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 86,
   "id": "4309d0b5",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "def most_frequent_letters(string):\n",
    "    return None"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 87,
   "id": "52228828",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 在这解答"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c6354c44",
   "metadata": {
    "tags": []
   },
   "source": [
    "这个例子可以测试你的函数："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 88,
   "id": "3bf2aa0d",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "most_frequent_letters('brontosaurus')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2ca1e337",
   "metadata": {
    "tags": []
   },
   "source": [
    "当你的函数正确运行，你可以使用以下代码读取小说*德古拉Dracula*，然后打印频率最高的字母。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 79,
   "id": "e4fbf5d9",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "download('https://www.gutenberg.org/cache/epub/345/pg345.txt');"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "817ec689",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "string = open('pg345.txt').read()\n",
    "most_frequent_letters(string)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "211c09c9",
   "metadata": {
    "tags": []
   },
   "source": [
    "根据Zim的“密码和秘密写作”，在英语中字母的频率降序为\"ETAONRISH\"。这个顺序与*Dracula*中的统计结果相差多少？"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cbe9933e",
   "metadata": {},
   "source": [
    "### 练习\n",
    "\n",
    "在之前的练习中，我们通过对单词排序，检查两个单词排序之后是否相同来判断它们是否为变位词。现在让我们挑战更难的问题。\n",
    "\n",
    "我们将编写程序，接受单词的列表，打印所有的变位词列表。以下是部分示例输出：\n",
    "\n",
    "    ['deltas', 'desalt', 'lasted', 'salted', 'slated', 'staled']\n",
    "    ['retainers', 'ternaries']\n",
    "    ['generating', 'greatening']\n",
    "    ['resmelts', 'smelters', 'termless']\n",
    "\n",
    "提示：对于单词列表中的每个单词，排序字母并重新拼接为字符串。创建一个字典，从这个排过序的字符串映射到变位词列表。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4b9ed2a8",
   "metadata": {
    "tags": []
   },
   "source": [
    "以下单元格下载`words.txt`并生成单词列表。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 80,
   "id": "941719c1",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "download('https://raw.githubusercontent.com/AllenDowney/ThinkPython/v3/words.txt');"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 84,
   "id": "d2ec641b",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "word_list = open('words.txt').read().split()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e4cc2c8c",
   "metadata": {
    "tags": []
   },
   "source": [
    "以下是我们之前使用的`sort_word`函数。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 85,
   "id": "7ae29f73",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "def sort_word(word):\n",
    "    return ''.join(sorted(word))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 94,
   "id": "013819a5",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 在这解答"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "70faa9f5",
   "metadata": {
    "tags": []
   },
   "source": [
    "要寻找最多变位词的列表，你可以使用以下函数，接受一个键-值对，键是字符串，值是单词列表。函数返回列表的长度。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 86,
   "id": "fbf9ede3",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "def value_length(pair):\n",
    "    key, value = pair\n",
    "    return len(value)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dcda6e28",
   "metadata": {
    "tags": []
   },
   "source": [
    "我们可以使用该函数对字典项进行排序，找到最多的变位词列表。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 96,
   "id": "55435050",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "anagram_items = sorted(anagram_dict.items(), key=value_length)\n",
    "for key, value in anagram_items[-10:]:\n",
    "    print(value)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0b6d5add",
   "metadata": {
    "tags": []
   },
   "source": [
    "如果你想要知道最多的变位词列表，你可以使用以下循环打印："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 97,
   "id": "6a9320c2",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "longest = 7\n",
    "\n",
    "for key, value in anagram_items:\n",
    "    if len(value) > 1:\n",
    "        word_len = len(value[0])\n",
    "        if word_len > longest:\n",
    "            longest = word_len\n",
    "            print(value)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4fbe939e",
   "metadata": {},
   "source": [
    "### 练习\n",
    "\n",
    "编写函数`word_distance`，接受两个长度相等的单词，返回两个单词字母不相同的数量。\n",
    "\n",
    "提示：使用`zip`循环两个单词的成对字母。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8b48dbdc",
   "metadata": {
    "tags": []
   },
   "source": [
    "以下是函数的框架，包含文档测试。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3d5a75f8",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "def word_distance(word1, word2):\n",
    "    \"\"\"计算两个单词字母顺序不同的数量。\n",
    "\n",
    "    >>> word_distance(\"hello\", \"hxllo\")\n",
    "    1\n",
    "    >>> word_distance(\"ample\", \"apply\")\n",
    "    2\n",
    "    >>> word_distance(\"kitten\", \"mutton\")\n",
    "    3\n",
    "    \"\"\"\n",
    "    return None"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 99,
   "id": "a9816dde",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 在这解答"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 100,
   "id": "753a23c1",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "from doctest import run_docstring_examples\n",
    "\n",
    "def run_doctests(func):\n",
    "    run_docstring_examples(func, globals(), name=func.__name__)\n",
    "    \n",
    "run_doctests(word_distance)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "066eec59",
   "metadata": {},
   "source": [
    "### 练习\n",
    "\n",
    "置换是单词中字母的互换过程。一个单词交换两个字母后形成的单词与原单词是置换对。编写程序，寻找单词列表中的所有置换对。\n",
    "\n",
    "提示：置换对单词必然互为变位词。\n",
    "\n",
    "致谢：本练习受<http://puzzlers.org>的示例启发。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 101,
   "id": "57649075",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 在这解答"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6a028806",
   "metadata": {
    "tags": []
   },
   "source": [
    "### 练习\n",
    "\n",
    "这是不属于本书的额外练习，比本章其他练习更有挑战性，所以你可以询问虚拟助手要求帮助，或者在你阅读后几章后再解决。\n",
    "\n",
    "以下是一个Car Talk字谜(<http://www.cartalk.com/content/puzzlers>):\n",
    "\n",
    "> 当你依次删掉1个字母，剩下的依然是合法的英语单词的最长单词是什么？\n",
    "> 被删除的字母位置任意，但不能修改剩余字母的顺序。最终剩余的一个字母也是字典上能查到的英语单词。\n",
    ">\n",
    "> 一个例子是Sprite。移除r得到spite,移除e得到spit,移除s得到pit,然后是it和I.\n",
    "\n",
    "编写程序，找到能够按这种方式删减的所有单词，然后找到最长的单词。\n",
    "\n",
    "这个练习比多数练习都更难，以下是一些建议：\n",
    "\n",
    "1. 编写一个函数，接受单词并计算移除一个单词后能够形成的所有单词。这些是该单词的“孩子”。\n",
    "2. 一个单词的任意孩子可被删减，则该单词可被删减，符合递归形式。对于基准条件，你可以认为空字符串是可删减的。\n",
    "3. 我们使用的单词列表中没有一个字母组成的单词，所以你可以添加I和a。\n",
    "4. 要改善程序的性能，你可能想要将已知可删减的单词进行备忘。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 102,
   "id": "c19bf833",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 在这解答"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 103,
   "id": "2d9764d6",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 在这解答"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 104,
   "id": "5e4f5d8e",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 在这解答"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 105,
   "id": "27d311dd",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 在这解答"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 106,
   "id": "68c27c7e",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 在这解答"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a7f4edf8",
   "metadata": {
    "tags": []
   },
   "source": [
    "[Think Python: 3rd Edition](https://allendowney.github.io/ThinkPython/index.html)\n",
    "\n",
    "Copyright 2024 [Allen B. Downey](https://allendowney.com)\n",
    "\n",
    "Code license: [MIT License](https://mit-license.org/)\n",
    "\n",
    "Text license: [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International](https://creativecommons.org/licenses/by-nc-sa/4.0/)"
   ]
  }
 ],
 "metadata": {
  "celltoolbar": "Tags",
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
